package com.ask.service.businessService.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ask.dto.RedisData;
import com.ask.dto.Result;
import com.ask.entity.Shop;
import com.ask.mapper.ShopMapper;
import com.ask.service.businessService.IShopService;
import com.ask.service.utilService.BloomFilterService;
import com.ask.utils.CacheClient;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static com.ask.constants.RedisConstant.*;

@Service
@Slf4j
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private ShopMapper shopMapper;

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    @Autowired
    private CacheClient cacheClient;

    @Autowired
    private BloomFilterService bloomFilterService;
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 根据id查询店铺信息
     *
     * @param id
     * @return
     */
    @Override
    public Result queryById(Long id) {
        Shop shop = cacheClient.
                queryWithPassThrough(CACHE_SHOP_KEY, Shop.class, id,
                        this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
        if (shop == null)
            return Result.fail("店铺不存在！");
        return Result.success(shop);
    }


    /**
     * 根据id查询（未解决缓存穿透 与 缓存击穿）
     * @param id
     * @return
     */
    public Result queryById_normal_handling(Long id) {
        String key = CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 缓存未命中
        if (StrUtil.isBlank(shopJson)) {
            // 查询数据库
            Shop shop = getById(id);
            if (shop == null) {
                // 店铺不存在，返回错误信息
                return Result.fail("店铺不存在！");
            }
            // 店铺存在，存储至redis
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
        }
        // 缓存命中直接返回
        return Result.success(JSONUtil.toBean(shopJson, Shop.class));
    }

    public Result queryById_WithMutex(Long id) {
        // 构建Redis缓存的键，格式为前缀加上店铺ID
        String key = CACHE_SHOP_KEY + id;

        // 1. 从Redis中查询店铺的缓存信息
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        // 2. 判断缓存中是否存在店铺信息
        if (StrUtil.isNotBlank(shopJson)) {
            // 如果缓存中存在有效数据，直接将JSON字符串转换为Shop对象并返回
            return Result.success(JSONUtil.toBean(shopJson, Shop.class));
        }

        // 检查缓存中是否存储了空值（表示店铺不存在）
        if (shopJson != null) {
            // 如果缓存中存储了空值，返回null，避免缓存穿透
            return Result.fail("店铺不存在！");
        }

        // 实现缓存重建
        // 获取锁，防止缓存击穿
        String lockKey = "lock:shop" + id;
        Shop shop = null;
        try {
            boolean islock = tryLock(lockKey);
            if (!islock) {
                // 如果未获取到锁，稍等片刻后重试
                Thread.sleep(1);
                return queryById_WithMutex(id);
            }

            // 3. 锁定成功后，从数据库中查询店铺信息
            shop = getById(id);
            // 模拟重建的延迟
            //Thread.sleep(200);
//            if (shop == null) {
//                // 如果数据库中也不存在该店铺，将空值写入Redis缓存，设置较短的过期时间
//                stringRedisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
//                return Result.fail("店铺不存在！");
//            }

            // 4. 将数据库查询到的店铺信息写入Redis缓存，并设置过期时间
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            // 处理线程中断异常
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            unLock(lockKey);
        }
        // 返回查询到的店铺信息
        return Result.success(shop);
    }

    private void checkAndPreheatCache(Long id) {
        String key = CACHE_SHOP_KEY + id;
        Long ttl = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        if (ttl != null && ttl <= 10) {
            new Thread(() -> preheatCache(id)).start();
        }
    }

    /**
     * 预热缓存
     * @param id
     */
    private void preheatCache(Long id) {
        String key = CACHE_SHOP_KEY + id;
        Shop shop = getById(id);
        if (shop != null) {
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        }
    }

    /**
     * 根据id查询（解决缓存穿透）
     *
     * @param id
     * @return
     */
    public Result queryById_cache_miss_handling(Long id) {
        // 1. 布隆过滤器中是否存在
        if (!bloomFilterService.mightContain(id)) {
            return Result.fail("店铺不存在！---(布隆过滤器中不存在)");
        }

        // 2. 查询缓存
        String key = CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 缓存命中(非null)，且不为空值
        if (shopJson != null && !shopJson.equals("\"" + EMPTY_CACHE + "\"")) {
            return Result.success(JSONUtil.toBean(shopJson, Shop.class), "缓存命中，且非空值"); // 查询成功
        }

        // 3. 缓存命中但为空值(说明不存在)
        if (shopJson != null) {
            return Result.fail("店铺不存在！（缓存命中但为空值）");
        }

        // 4. 缓存未命中，查询数据库
        Shop shop = getById(id);

        // 4.1 查询成功，存储至redis，返回成功信息
        if (shop != null) {
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
            return Result.success(shop, "数据库命中，存储至redis");
        }
        // 4.2 数据库中不存在，缓存空数据，返回错误信息
        else {
            redisTemplate.opsForValue().set(key, EMPTY_CACHE, EMPTY_CACHE_TTL, TimeUnit.MINUTES);
            return Result.fail("店铺不存在！（数据库中不存在）");
        }
    }


    /**
     * 根据id查询（互斥锁解决缓存击穿）
     *
     * @param id
     * @return
     */
    @Override
    public Result queryById_with_mutex_cache_busting(Long id) {
        // 1. 查询缓存
        String key = CACHE_SHOP_KEY + id;
        String cacheJson = stringRedisTemplate.opsForValue().get(key);
        // 2. 缓存存在直接返回
        if (cacheJson != null && !cacheJson.equals("\"" + EMPTY_CACHE + "\""))
            return Result.success(JSONUtil.toBean(cacheJson, Shop.class));

        // 3. 缓存为空值返回错误信息
        if (cacheJson != null) {
            return Result.fail("店铺不存在！");
        }

        // 4. 缓存不存在，查询数据库，重建缓存（加锁）
        String lockKey = "lock:shop" + id;
        Shop shop = null;
        try {
            if (!tryLock(lockKey)) {
                // 如果未获取到锁，稍等片刻后重试
                Thread.sleep(50);
                return queryById_with_mutex_cache_busting(id);

            } else {
                // 从数据库查询数据
                shop = getById(id);
                // 5. 数据库不存在，存储空值，返回错误信息
                if (shop == null) {
                    stringRedisTemplate.opsForValue().set(key, EMPTY_CACHE, EMPTY_CACHE_TTL, TimeUnit.MINUTES);
                    return Result.fail("店铺不存在！");
                }
                // 6. 数据库存在，存储至redis
                else {
                    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
                }

            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // （解锁）
            unLock(lockKey);
        }
        return Result.success(shop);
    }

    /**
     * 互斥锁上锁
     */
    private boolean tryLock(String key) {
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
        return BooleanUtil.isTrue(lock);
    }

    /**
     * 互斥锁解锁
     *
     * @param key
     */
    private void unLock(String key) {
        stringRedisTemplate.delete(key);
    }

    /**
     * 商品查询（逻辑过期解决缓存击穿）
     *
     * @param id
     * @return
     */
    public Result queryById_with_LogicalExpire(Long id) {
        // 1.查询redis缓存
        String key = CACHE_SHOP_KEY + id;
        String redisDataJson = stringRedisTemplate.opsForValue().get(key);
        // 缓存未命中，返回空
        if (StrUtil.isBlank(redisDataJson))
            return Result.success(null);

        // 获取过期时间
        RedisData redisData = JSONUtil.toBean(redisDataJson, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);

        // 未过期，返回店铺信息
        if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
            return Result.success(shop);
        }

        // 过期，尝试获取锁
        String lockKey = "lock:shop" + id;
        boolean getLock = tryLock(lockKey);
        // 获取锁成功
        if (getLock) {
            // 拿到锁，开启独立线程进程缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    saveShop2Redis(id, 20L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    unLock(lockKey);
                }
            });
        }

        // 获取锁失败，直接返回过期的店铺信息
        return Result.success(shop);
    }


    /**
     * 直接从数据库中查询
     *
     * @param id
     * @return
     */
    @Override
    public Result queryByIdWithOutRedis(Long id) {
        Shop shop = getById(id);
        if (shop == null)
            return Result.fail("店铺不存在！");
        return Result.success(shop);
    }

    /**
     * 跟新店铺信息
     *
     * @param shop
     * @return
     */
    @Override
    public Result updateWithCache(Shop shop) {
        if (shop.getId() == null)
            return Result.fail("店铺id不能为空！");
        // 先跟新数据库
        updateById(shop);
        // 再删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());
        return Result.success();
    }


    @Override
    public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
        // 如果未提供坐标，仅根据店铺类型分页查询店铺信息
        Page<Shop> page = query()
                .eq("type_id", typeId) // 条件：店铺类型
                .page(new Page<>(current, 5)); // 分页：每页5个
        return Result.success(page.getRecords()); // 返回查询结果
        // 检查是否提供了地理坐标
//        if (x == null || y == null) {
//            // 如果未提供坐标，仅根据店铺类型分页查询店铺信息
//            Page<Shop> page = query()
//                    .eq("type_id", typeId) // 条件：店铺类型
//                    .page(new Page<>(current, 5)); // 分页：每页5个
//            return Result.success(page.getRecords()); // 返回查询结果
//        }

//        // 计算分页的起始和结束索引
//        int from = (current - 1) * DEFAULT_PAGE_SIZE;
//        int end = current * DEFAULT_PAGE_SIZE;
//        // 构造Redis存储店铺地理位置的Key
//        String key = "shop:geo:" + typeId;
//
//        // 使用Redis的GEOSEARCH命令按距离查询店铺
//        GeoResults<RedisGeoCommands.GeoLocation<String>> results = stringRedisTemplate.opsForGeo()
//                .search(
//                        key, // Redis中的Key
//                        GeoReference.fromCoordinate(x, y), // 参考点为用户当前位置
//                        new Distance(5000), // 查询半径：5000米
//                        RedisGeoCommands.GeoSearchCommandArgs.newGeoSearchArgs().includeDistance().limit(end) // 包含距离并限制结果数
//                );
//
//        // 如果查询结果为空或结果数不够分页起始位置，返回空列表
//        if (results == null || results.getContent().size() <= from) {
//            return Result.success(Collections.emptyList());
//        }
//
//        // 获取查询结果列表
//        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> list = results.getContent();
//        // 创建店铺ID列表和距离映射
//        List<Long> ids = new ArrayList<>(list.size());
//        Map<String, Distance> distanceMap = new HashMap<>(list.size());
//
//        // 遍历分页后的结果，提取店铺ID和距离信息
//        list.stream().skip(from).forEach(result -> {
//            String shopIdStr = result.getContent().getName(); // 获取店铺ID
//            ids.add(Long.valueOf(shopIdStr)); // 添加到ID列表
//            distanceMap.put(shopIdStr, result.getDistance()); // 添加到距离映射
//        });
//
//        // 根据ID列表从数据库查询店铺详细信息，并保持排序
//        String idStr = StrUtil.join(",", ids); // 将ID列表转为字符串
//        List<Shop> shops = query().in("id", ids).last("order by field(id," + idStr + ")").list();
//
//        // 设置店铺的距离信息
//        for (Shop shop : shops) {
//            shop.setDistance(distanceMap.get(shop.getId().toString()).getValue());
//        }
//
//        // 返回包含距离的店铺信息列表
//        return Result.success(shops);
    }


    public void saveShop2Redis(Long id, Long expireSeconds) throws InterruptedException {
        // 查询店铺数据
        Shop shop = getById(id);
        Thread.sleep(200);
        // 封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        // 写入redis
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
    }


}
